import * as THREE from "three";

export class ViewHelper extends THREE.Object3D {
  // editorCamera: THREE.Camera;
  // private readonly container: HTMLElement;

  // private readonly panel: HTMLElement;
  // private readonly point = new THREE.Vector3();
  // private readonly dim = 128;

  // private readonly posXAxisHelper: THREE.Sprite;
  // private readonly posYAxisHelper: THREE.Sprite;
  // private readonly posZAxisHelper: THREE.Sprite;
  // private readonly negXAxisHelper: THREE.Sprite;
  // private readonly negYAxisHelper: THREE.Sprite;
  // private readonly negZAxisHelper: THREE.Sprite;

  // private readonly camera: THREE.OrthographicCamera;
  // private readonly interactiveObjects: THREE.Object3D[];
  // private readonly raycaster = new THREE.Raycaster();
  // private readonly mouse = new THREE.Vector2();

  // render: (renderer: THREE.WebGLRenderer) => void;
  // handleClick: (event: MouseEvent) => void;
  // update: (delta: number) => void;

  // animating: boolean;
  // center: THREE.Vector3;

  constructor(editorCamera, container) {
    super();

    this.point = new THREE.Vector3();
    this.dim = 128;

    this.raycaster = new THREE.Raycaster();
    this.mouse = new THREE.Vector2();

    this.animating = false;
    this.center = new THREE.Vector3(0, 0, 0);

    this.container = container;
    this.editorCamera = editorCamera;

    const panel = document.createElement("div");
    panel.style.position = "absolute";
    panel.style.right = "0px";
    panel.style.top = "0px";
    panel.style.height = "128px";
    panel.style.width = "128px";
    panel.addEventListener("mouseup", (event) => {
      event.stopPropagation();
      event.preventDefault();
      this.handleClick(event);
    });
    panel.addEventListener("mousedown", (event) => {
      event.stopPropagation();
    });
    container.appendChild(panel);
    this.panel = panel;

    const interactiveObjects = [];
    const camera = new THREE.OrthographicCamera(-2, 2, 2, -2, 0, 4);
    camera.position.set(0, 0, 2);
    this.camera = camera;

    const geometry = new THREE.PlaneGeometry(1.4, 1.4);

    {
      const pi = Math.PI * 0.5;
      
      const plane1 = new THREE.Mesh(geometry, getSpriteMaterial("前"));
      plane1.position.x = 0.7;
      plane1.rotation.y = pi;
      plane1.userData.type = "+X";
      interactiveObjects.push(plane1);
      this.posXAxisHelper = plane1;
      this.add(plane1);

      const plane2 = new THREE.Mesh(geometry, getSpriteMaterial("后"));
      plane2.position.x = -0.7;
      plane2.rotation.y = -pi;
      plane2.userData.type = "-X";
      interactiveObjects.push(plane2);
      this.negXAxisHelper = plane2;
      this.add(plane2);
      //上
      const plane3 = new THREE.Mesh(geometry, getSpriteMaterial("左"));
      plane3.position.z = 0.7;
      plane3.userData.type = "+Z";
      interactiveObjects.push(plane3);
      this.posZAxisHelper = plane3;
      this.add(plane3);

      //下
      const plane4 = new THREE.Mesh(geometry, getSpriteMaterial("右"));
      plane4.position.z = -0.7;
      plane4.rotation.y = 2 * pi;
      plane4.userData.type = "-Z";
      interactiveObjects.push(plane4);
      this.negZAxisHelper = plane4;
      this.add(plane4);

      const plane5 = new THREE.Mesh(geometry, getSpriteMaterial("上"));
      plane5.position.y = 0.7;
      plane5.rotation.x = -pi;
      plane5.userData.type = "+Y";
      interactiveObjects.push(plane5);
      this.posYAxisHelper = plane5;
      this.add(plane5);

      const plane6 = new THREE.Mesh(geometry, getSpriteMaterial("下"));
      plane6.position.y = -0.7;
      plane6.rotation.x = pi;
      plane6.userData.type = "-Y";
      interactiveObjects.push(plane6);
      this.negYAxisHelper = plane6;
      this.add(plane6);

      const dirx = new THREE.Vector3(1, 0, 0);
      const diry = new THREE.Vector3(0, 1, 0);
      const dirz = new THREE.Vector3(0, 0, 1);
      dirx.normalize();
      diry.normalize();
      dirz.normalize();
      const origin = new THREE.Vector3(0, 0, 0);

      this.add(new THREE.ArrowHelper(dirx, origin, 2, 0xff3653));
      this.add(new THREE.ArrowHelper(diry, origin, 2, 0x8adb00));
      this.add(new THREE.ArrowHelper(dirz, origin, 2, 0x2c8fff));
    }

    this.interactiveObjects = interactiveObjects;

    const targetPosition = new THREE.Vector3();
    const targetQuaternion = new THREE.Quaternion();

    const q1 = new THREE.Quaternion();
    const q2 = new THREE.Quaternion();
    let radius = 0;
    const turnRate = 2 * Math.PI; // turn rate in angles per second
    const dummy = new THREE.Object3D();

    this.render = (renderer) => {
      this.quaternion.copy(this.editorCamera.quaternion).invert();
      this.updateMatrixWorld();

      const point = this.point;
      point.set(0, 0, 1);
      point.applyQuaternion(this.editorCamera.quaternion);

      if (point.x >= 0) {
        this.posXAxisHelper.material.opacity = 1;
        this.negXAxisHelper.material.opacity = 0.5;
      } else {
        this.posXAxisHelper.material.opacity = 0.5;
        this.negXAxisHelper.material.opacity = 1;
      }

      if (point.y >= 0) {
        this.posYAxisHelper.material.opacity = 1;
        this.negYAxisHelper.material.opacity = 0.5;
      } else {
        this.posYAxisHelper.material.opacity = 0.5;
        this.negYAxisHelper.material.opacity = 1;
      }

      if (point.z >= 0) {
        this.posZAxisHelper.material.opacity = 1;
        this.negZAxisHelper.material.opacity = 0.5;
      } else {
        this.posZAxisHelper.material.opacity = 0.5;
        this.negZAxisHelper.material.opacity = 1;
      }

      const x = this.container.offsetWidth - this.dim;
      const y = this.container.offsetHeight - this.dim;
      const v4 = renderer.getViewport(new THREE.Vector4());
      renderer.setViewport(x, y, this.dim, this.dim);
      renderer.render(this, this.camera);
      renderer.setViewport(v4);
    };

    this.handleClick = (event) => {
      if (this.animating === true) return false;

      this.mouse.x = (event.offsetX / this.dim) * 2 - 1;
      this.mouse.y = -(event.offsetY / this.dim) * 2 + 1;

      this.raycaster.setFromCamera(this.mouse, this.camera);

      const intersects = this.raycaster.intersectObjects(
        this.interactiveObjects
      );

      if (intersects.length > 0) {
        const intersection = intersects[0];
        const object = intersection.object;
        prepareAnimationData(object, this.center);

        this.animating = true;

        return true;
      } else {
        return false;
      }
    };
    this.update = function (delta) {
      const step = delta * turnRate;
      const focusPoint = this.center;

      q1.rotateTowards(q2, step);
      editorCamera.position
        .set(0, 0, 1)
        .applyQuaternion(q1)
        .multiplyScalar(radius)
        .add(focusPoint);

      editorCamera.quaternion.rotateTowards(targetQuaternion, step);

      if (q1.angleTo(q2) === 0) {
        this.animating = false;
      }
    };
    function prepareAnimationData(object, focusPoint) {
      switch (object.userData.type) {
        case "+X":
          targetPosition.set(1, 0, 0);
          targetQuaternion.setFromEuler(new THREE.Euler(0, Math.PI * 0.5, 0));
          break;

        case "+Y":
          targetPosition.set(0, 1, 0);
          targetQuaternion.setFromEuler(new THREE.Euler(-Math.PI * 0.5, 0, 0));
          break;

        case "+Z":
          targetPosition.set(0, 0, 1);
          targetQuaternion.setFromEuler(new THREE.Euler());
          break;

        case "-X":
          targetPosition.set(-1, 0, 0);
          targetQuaternion.setFromEuler(new THREE.Euler(0, -Math.PI * 0.5, 0));
          break;

        case "-Y":
          targetPosition.set(0, -1, 0);
          targetQuaternion.setFromEuler(new THREE.Euler(Math.PI * 0.5, 0, 0));
          break;

        case "-Z":
          targetPosition.set(0, 0, -1);
          targetQuaternion.setFromEuler(new THREE.Euler(0, Math.PI, 0));
          break;

        default:
          console.error("ViewHelper: Invalid axis.");
      }

      radius = editorCamera.position.distanceTo(focusPoint);
      targetPosition.multiplyScalar(radius).add(focusPoint);

      dummy.position.copy(focusPoint);

      dummy.lookAt(editorCamera.position);
      q1.copy(dummy.quaternion);

      dummy.lookAt(targetPosition);
      q2.copy(dummy.quaternion);
    }
  }
}

function getAxisMaterial(color) {
  return new THREE.MeshBasicMaterial({
    color: color,
    toneMapped: false,
  });
}

function getSpriteMaterial(text, color = new THREE.Color("#f8f8f8")) {
  const canvas = document.createElement("canvas");
  canvas.width = 128;
  canvas.height = 128;

  const context = canvas.getContext("2d");
  context.strokeRect(0, 0, 128, 128);
  context.fillStyle = color.getStyle();
  context.fillRect(1, 1, 126, 126);

  if (text) {
    context.font = "64px Arial";
    context.textAlign = "center";
    context.fillStyle = "#83aad0";
    context.fillText(text, 64, 85);
  }

  const texture = new THREE.CanvasTexture(canvas);

  return new THREE.MeshBasicMaterial({
    map: texture,
  });
}
